Attack via Dynamic Linker
Since the ENV variables are "hidden", they are extremely dangerous.
Indeed, any users can set environment vars and therefore they become part of attack surface on Set-UID programs.
The following picture summarize the attack surface created by ENV vars.
Linking is an important stage since it finds the external library code referenced in the program and links such code to the program.
It can be done at
Since dynamic linking use ENV variables to operate, they can be exploited to compromise Set-UID programs.
In this case, the linker combines the program's code and the library code containing the referenced function and all the functions it depends on.
In this way, the executable is self-contained, without any missing code.
The size of the static compiled program is bigger than the a dynamic compiled program.
#ldd shows the shared libraries a program depends on
$ ldd hello_static
not a dynamic executable
In this case, the linking is performed during runtime. The libraries that support dynamic linking are called shared libraries.
Steps:
main() function can be executed#ldd shows the shared libraries a program depends on
$ ldd hello_dynamic
linux-gate.so.1 => (0xb774b000) #for system calls, needed by all program
libc.so.6 => /lib/i836-linux-gnu/libc.so.6 (0xb758b000) #libc library
/lib/ld-linux.so.2 (0xb774c000) #dynamic linker which is a shared library
Despite dynamic linking saves memory, with this linking part of the program's code is undecided during the compilation time (where devs have full control).
Indeed, the missing code is decided during runtime, when users, who might be naughty, are in control. This could compromise the integrity pf privileged program.
LD_PRELOAD & LD_LIBRARY_PATHThe dynamic linker searches some default folders for the library functions used by program. However, users can specify additional search places using LD_PRELOAD and LD_LIBRARY_PATH ENV vars.
LD_PRELOAD contains. a list of shared libraries which will be searched first by the linkerLD_LIBRARY_PATHSince they are ENV vars, they can be set by users and this gives them the opportunity to control the outcome of the linking process.
This is a problem in the context of Set-UID program.
Suppose we have an file mytest.c that calls sleep function, which is dynamically linked.
We implement our own sleep() function:
#include <stdio.h>
/* sleep.c */
void sleep(int s){
printf("Not sleeping!\n");
}
We need to:
LD_PRELOAD env varbisca@ubuntu:$ gcc -c sleep.c
bisca@ubuntu:$ gcc -shared -o libmylib.so.1.0.1 sleep.o
bisca@ubuntu:$ LD_PRELOAD = ./libmylib.so.1.0.1 # add export to pass to all created shells
bisca@ubuntu:$ ./mytest
Not sleeping! #Our library get invoked
bisca@ubuntu:$ unset LD_PRELOAD
bisca@ubuntu:$ ./mytest
bisca@ubuntu:$
Example with Set-UID Program
Let's try with mytest as Set-UID program.
bisca@ubuntu:$ sudo chown root mytest
bisca@ubuntu:$ sudo chmod 4755 mytest
bisca@ubuntu:$ LD_PRELOAD = ./libmylib.so.1.0.1 # add export to pass to all created shells
bisca@ubuntu:$ ./mytest
bisca@ubuntu:$
WHY???
This is due to the countermeasure implemented by the dynamic linker: it ignores LD_PRELOAD and LD_LIBRARY_PATH ENV vars when EUID and RUID differ
A program may invoke an external program and even though the application may not use ENV vars, the invoked program might.
There's 2 approaches:
exec() family of functions, which call execve() and run the program directlysystem() create a child by fork() and it uses execve() to run /bin/sh and eventually the shell created runs the program.PATH ENV VariableShell programs behavior is affected by many ENV vars, such as PATH var.
As said before, if a shell runs a command and the absolute path is not provided, it uses the PATH Env var to locate the command.
We firstly consider the following code (vul.c).
#include <stdlib.h>
int main (){
system("cal");
}
Here we want to execute the cal program, but since no absolute path is provided, by manipulating the PATH env var, we can force the execution of another program.
Let's create a malicious program called cal to.
int main(){
system("/bin/dash")
}
Suppose that, vul is a Set-UID Program
#No attack
bisca@ubuntu:$ vul
bisca@ubuntu:$
December
....
Now we set the PATH value with the current directory.
bisca@ubuntu:$ export PATH=.$PATH
bisca@ubuntu:$ vul
#From now on we are in a root shell
root@ubunutu: id
uid=1000(seed) .. euid=0(root)
Compare to system(), execve()'s attack surface is smaller, since execve() does not invoke a shell.
Therefore, in privileged program, we should choose execve() function.
Programs often use functions form external libraries. If these functions use env vars, they add to the attack surface.
Locale subsystem is a set of databases and library functions.
The databases store language and country-specific information.
The function are used to store, retrieve and manage such information.
When a program needs to display a message to user, it may want to display the massage in user's native language.
So, every time a message needs to be printed out, the program that use the provided library functions to ask the corresponding translated message.
To do so, in Unix we use gettext() and caropen() in the libc library.
int main(int argc, char** argv){
if(argc > 1){
printf(gettext("Usage %s filename"),argv[0]);
}
printf("Normal exec...");
}
Since Locale subsystem needs to know the user''s language as well as where to find the databases, it relies on the following env vars:
LANGLANGUAGE NLSPATH LOCPATH LC_ALL MESSAGESAs usual, env vars can be set by users, so the result of gettext() can be manipulated. By exploiting the format string vulnerabilities, attackers can take the control of a possible privileged program.
The countermeasure lies with the library author: there might be some check within the library code.
Programs may directly use env vars. This is clearly a possible attack surface.
getenv() on Application CodeSuppose we want to save the information of the PWD env var into an array, without checking the input length before the copy (this means potential buffer overflow)
/* prog.c */
#include <stdio.h>
#include <stdlib.h>
int main(void){
char arr[64];
char *ptr;
ptr = getenv("PWD"); // Used to know the current directory
if(ptr != NULL){
sprintf(arr,"Present working directory is: %s",ptr); //Copy
printf("%s\n"arr);
}
return 0;
}
PWD env var contains the name of the folder from where the process starts. This value comes from the shell program.
So, when we change folders, the shell program keeps updating its shell variable PWD. However, users can change the shell variable ourselves.
$ pwd
/home/seed/temp
$ echo $PWD
/home/seed/temp
$ cd ..
$ echo $PWD
/home/seed
$ cd /
$ echo $PWD
/
$ PWD = xyz
$ pwd
/
$ echo $PWD
xyz
Therefore, when a command is executed from the shell, a new process will be created and the shell will set the new process's env var PWD using its shell var PWD.
When env vars are used by Set-UID programs, they must be sanitized properly.
Devs, may choose a more secure version of getenv(), such as secure_getenv(), which works as the not safe one, but it returns NULL when "secure execution" (EUID and RUI don't match) is required.